package org.west.sky.scripture.imports.core.base;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.read.metadata.holder.ReadRowHolder;
import com.alibaba.excel.util.ListUtils;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.validator.HibernateValidator;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.groups.Default;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;

/**
 * @author: chz
 * @date: 2023/4/13
 * @description:
 */
@Slf4j
public class ImportDataSyncListener<T extends ImportData> extends AnalysisEventListener<T> {

    private final Class<T> tClass;

    private final Consumer<List<T>> consumer;
    private final List<T> dataList = ListUtils.newArrayList();
    private final Validator validator;
    private Map<String, Integer> fieldColumnMapping;

    private final List<String> validateColumns;

    public ImportDataSyncListener(final Consumer<List<T>> consumer, Class<T> tClass) {
        this(consumer, null, tClass);
    }

    public ImportDataSyncListener(final Consumer<List<T>> consumer, List<String> validateColumns, Class<T> tClass) {
        this(consumer, validateColumns, Validation.byProvider(HibernateValidator.class)
                .configure()
                .failFast(false)
                .buildValidatorFactory()
                .getValidator(), tClass);
    }

    public ImportDataSyncListener(final Consumer<List<T>> consumer, List<String> validateColumns, final Validator validator, Class<T> tClass) {
        this.validator = validator;
        this.consumer = consumer;
        this.tClass = tClass;
        this.validateColumns = validateColumns;
    }


    @Override
    public void invoke(T data, AnalysisContext analysisContext) {
//        log.info("获取到数据：{}", data.toString());
        // 清除错误信息
        data.setErrorMsg(null);
        this.validate(data, analysisContext);
//        log.info("数据校验结果：{}", data.getErrorMsg());
        dataList.add(data);
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        consumer.accept(dataList);
//        dataList.clear();
    }

    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        log.info("获取到表头：{}", headMap.toString());
        if (!ImportValidator.validatorExcelHeadsNoIndex(headMap, tClass.getDeclaredFields())) {
            throw new BusinessException("文件表头字段与模板不一致!");
        }
        super.invokeHeadMap(headMap, context);
    }

    @Override
    public void onException(Exception exception, AnalysisContext context) throws Exception {
        log.info("解析失败：{}", exception.getMessage());
        ReadRowHolder readRowHolder = context.readRowHolder();
        Map<Integer, ReadCellData<?>> cellDataMap = (Map) readRowHolder.getCellMap();
        readRowHolder.setCurrentRowAnalysisResult(cellDataMap);
        int rowIndex = readRowHolder.getRowIndex();
        int currentHeadRowNumber = context.readSheetHolder().getHeadRowNumber();
        //是否表头
        boolean isHead = rowIndex < currentHeadRowNumber;
        if (isHead) {
            throw new BusinessException("文件表头字段与模板不一致");
        }
        int columIndex = -1;
        String fieldType = "正确";

        if (exception instanceof ExcelDataConvertException) {
            ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException) exception;
            log.error("第{}行，第{}列解析异常", excelDataConvertException.getRowIndex() + 1, excelDataConvertException.getColumnIndex() + 1);
            columIndex = excelDataConvertException.getColumnIndex() + 1;
            Class<?> type = excelDataConvertException.getExcelContentProperty().getField().getType();
            if (type == LocalDate.class) {
                fieldType = "日期";
            } else if (type == LocalDateTime.class) {
                fieldType = "时间";
            } else if (type == Integer.class) {
                fieldType = "整数";
            } else if (type == BigDecimal.class) {
                fieldType = "数字";
            }
        }
        T errorData = tClass.getDeclaredConstructor().newInstance();
        errorData.setCorrect(false);
        errorData.setErrorMsg(columIndex > 0 ? StrUtil.format("第{}列数据格式有误,不是{}类型", columIndex, fieldType) : "数据格式有误");
        dataList.add(errorData);
    }

    /**
     * 通过属性从context获取列号
     *
     * @param field   属性名称
     * @param context excel上下文
     * @return 列号
     */
    private Integer getColumnIndex(final String field, final AnalysisContext context) {
        if (Objects.nonNull(this.fieldColumnMapping)) {
            return this.fieldColumnMapping.get(field);
        }
        synchronized (this) {
            if (Objects.nonNull(this.fieldColumnMapping)) {
                return this.fieldColumnMapping.get(field);
            }
            this.fieldColumnMapping = new HashMap<>(16);
            final Map<Integer, Head> excelFileHead = context.readSheetHolder().excelReadHeadProperty().getHeadMap();
            for (final Map.Entry<Integer, Head> entry : excelFileHead.entrySet()) {
                this.fieldColumnMapping.put(entry.getValue().getFieldName(), entry.getKey());
            }
            return this.fieldColumnMapping.get(field);
        }
    }

    public void validate(final T data, final AnalysisContext context) {
        final Set<ConstraintViolation<T>> violationSet;
        if (CollectionUtil.isEmpty(validateColumns)) {
            violationSet = validator.validate(data, Default.class);
        } else {
            violationSet = new HashSet<>();
            validateColumns.forEach(field -> violationSet.addAll(validator.validateProperty(data, field, Default.class)));
        }
        if (CollectionUtil.isEmpty(violationSet)) {
            return;
        }
        String msg = violationSet.stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(";"));
        data.setCorrect(false);
        data.setErrorMsg(msg);
    }

    /**
     * Valid. 需求变更，需要一次输出所有错误
     *
     * @param data    the data
     * @param context the context
     */
    public void validateOld(final T data, final AnalysisContext context) {
        final Optional<ConstraintViolation<T>> violationOptional = validator.validate(data, Default.class).stream().findFirst();
        if (!violationOptional.isPresent()) {
            return;
        }
        final ConstraintViolation<T> violation = violationOptional.get();
        final String field = violation.getPropertyPath().toString();
        final String message = violation.getMessage();
        final Object value = violation.getInvalidValue();
        final Integer rowIndex = context.readRowHolder().getRowIndex();
        final int columnIndex = getColumnIndex(field, context) + 1;
        data.setCorrect(false);
        data.setErrorMsg("表格第" + columnIndex + "列数据错误:" + message);
    }

    /**
     * 判断整行单元格数据是否均为空
     */
    private boolean isLineNullValue(T data) {
        try {
            List<Field> fields = Arrays.stream(data.getClass().getDeclaredFields())
                    .filter(f -> f.isAnnotationPresent(ExcelProperty.class)).collect(Collectors.toList());
            List<Boolean> lineNullList = new ArrayList<>(fields.size());
            for (Field field : fields) {
                field.setAccessible(true);
                Object value = field.get(data);
                if (ObjectUtil.isNull(value)) {
                    lineNullList.add(Boolean.TRUE);
                } else {
                    lineNullList.add(Boolean.FALSE);
                }
            }
            return lineNullList.stream().allMatch(Boolean.TRUE::equals);
        } catch (Exception e) {
            log.error("读取数据行[{}]解析失败: {}", data, e.getMessage());
        }
        return true;
    }

}
